package projet_v1;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import projet_v1.items.ammo.Ammo;
import projet_v1.items.weapons.RechargeableWeapon;
import projet_v1.items.weapons.Weapon;
import projet_v1.simulation.Field;
import projet_v1.simulation.Location;
import projet_v1.simulation.Randomizer;
import projet_v1.simulation.Simulator;

/**
 * Human class, derives from Character
 * 
 * @author Laurent, Lavail, Roufast
 * 
 */

public class Human extends Character implements Actor {

	// Defaults HP
	public static final int HP_HUMANS = 100;
	// Default speed
	private static final int SPEED = 2;
	// The age to which a human can live.
	private static final int MAX_AGE = 100;
	// The age at which a human can start to breed.
	private static final int BREEDING_AGE = 16;
	// The likelihood of a human breeding.
	private static final double BREEDING_PROBABILITY = 0.4;// 0.5
	// The maximum number of births.
	private static final int MAX_LITTER_SIZE = 4;
	// A shared random number generator to control breeding.
	private static final Random rand = Randomizer.getRandom();

	// false, until a vampire bites this human
	private boolean hasBeenBitten;
	// the human will lose health if he's too hungry
	private int turnsSinceLastMeal;
	// the age of the human
	private int age;
	// the bag which may contain some weapons, cures or food
	private List<Item> bag = new ArrayList<Item>();

	/**
	 * Constructor of Human class. At the beginning of the game, humans just had
	 * dinner, and have not been bitten yet.
	 * 
	 * @param healthPoints
	 *            initial HP
	 */
	public Human(int healthPoints) {
		super(healthPoints, Breed.HUMAN);
		hasBeenBitten = false;
		turnsSinceLastMeal = 0;
		age = 0;
		bag = null;
	}

	/**
	 * Another constructor of Human class. At the beginning of the game, humans
	 * just had dinner, and have not been bitten yet. This constructor add a
	 * field and a location.
	 * 
	 * @param healthPoints
	 *            initial HP
	 * @param f
	 *            : the current field.
	 * @param l
	 *            : the wanted location for the human.
	 */
	public Human(int healthPoints, Field f, Location l) {
		super(healthPoints, Breed.HUMAN, f, l, SPEED);
		hasBeenBitten = false;
		turnsSinceLastMeal = 0;
		age = rand.nextInt(18);
		bag = null;
	}

	/**
	 * Constructor of Human class. At the beginning of the game, humans just had
	 * dinner, and have not been bitten yet. The constructor add a bree, a field
	 * and a location.
	 * 
	 * @param healthPoints
	 *            initial HP
	 * @param b
	 *            : the wanted breed.
	 * @param f
	 *            : the current field.
	 * @param l
	 *            : the wanted location for the human.
	 */
	protected Human(int healthPoints, Breed breed, Field f, Location l) {
		super(healthPoints, breed, f, l, SPEED);
		hasBeenBitten = false;
		turnsSinceLastMeal = 0;
		age = rand.nextInt(18);
		bag = null;
	}

	// Accessors and mutators

	public boolean getHasBeenBitten() {
		return hasBeenBitten;
	}

	public void setHasBeenBitten(boolean hasBeenBitten) {
		this.hasBeenBitten = hasBeenBitten;
	}

	public void setTurnsSinceLastMeal(int i) {
		turnsSinceLastMeal = i;
	}

	public int getTurnsSinceLastMeal() {
		return turnsSinceLastMeal;
	}

	/**
	 * Generate a number representing the number of births, if it can breed.
	 * 
	 * @return The number of births (may be zero).
	 */
	protected int breed() {
		int births = 0;
		if ((age >= BREEDING_AGE)
				&& (rand.nextDouble() <= BREEDING_PROBABILITY)) {
			births = rand.nextInt(MAX_LITTER_SIZE) + 1;
		}
		return births;
	}

	/**
	 * Method triggered on each character at the end of each turn.
	 */
	public void endOfTurn() {
		// Increment the number of turns since the last time the human ate
		turnsSinceLastMeal++;
		// If the human is too hungry, he will lose health...
		if (turnsSinceLastMeal > 3) {
			reduceHealthPoints(2);
		}
	}

	/**
	 * Transform this human who has been bitten, into a blood-thirsty vampire.
	 * 
	 * @return a new object of class Vampire, with the same name and
	 *         healthpoints as this human; the new vampire is immediately
	 *         thirsty
	 */
	public Vampire turnIntoVampire() {
		Vampire v = new Vampire(this.healthPoints, this.field, this.location);
		v.setIsThirsty(true);
		return v;
	}

	/**
	 * Transform this dead human in zombie
	 * 
	 * @return a new object of class Zombie
	 */
	public Zombie turnIntoZombie() {
		Zombie z = new Zombie(Zombie.HP_ZOMBIES, this.field, this.location);
		return z;
	}

	/**
	 * @author Antoine Lavail
	 *
	 * Actions when a Human encounter someone.
	 */
	public void encounterCharacter(Character c) {
		if (c == null)
			throw new NullPointerException(
					"null character passed in encounterCharacter method of the human class.");
		if (c.getBreed().equals(Breed.ZOMBIE)
				|| c.getBreed().equals(Breed.VAMPIRE)) {
			if (this.containsWeapon()) {
				attack(c);
			}
		}
	}

	/**
	 * @author Antoine Lavail
	 * 
	 * Method whom determinate the damage that the human can do on another
	 * character.
	 * 
	 * @param c
	 *            : the attacked character.
	 */
	protected void attack(Character c) {
		if (c == null)
			throw new NullPointerException(
					"null character passed in attack method of the human class.");
		Weapon weapon = chooseBestWeapon(c.getBreed());
		if (weapon != null) {
			weapon.use(c);
			c.lastAttackedBy = this.getBreed();
			maj(weapon);
		}
	}

	/**
	 * @author Antoine Lavail
	 * 
	 * Throw away the weapon who is in the bag if there are no ammo anymore.
	 * 
	 * @param weapon
	 *            : The specified weapon.
	 */
	private void maj(Weapon weapon) {
		if (weapon == null)
			throw new NullPointerException(
					"null weapon passed in maj method of the human class.");
		if (!(weapon.hasAmmo()))
			bag.remove(weapon);
	}

	/**
	 * @author Antoine Lavail
	 * 
	 * Make this character act ­ that is: make it do whatever it wants/needs to
	 * do.
	 * 
	 * @param currentField
	 *            The field currently occupied.
	 * @param updatedField
	 *            The field to transfer to.
	 * @param newActors
	 *            A list to add newly born character to.
	 */
	public void act(Field currentField, Field updatedField,
			List<Actor> newActors) {
		if (currentField == null)
			throw new NullPointerException(
					"null currentField passed in act method of human class.");
		if (updatedField == null)
			throw new NullPointerException(
					"null updatedField passed in act method of human class.");

		incrementAge();
		// TODO : Gérer la faim de l'humain (cf. Fox)
		// Nb of actions allow for one "act" (equivalent of speed)

		if (isAlive()) {
			// Search items
			collectItems(currentField);
			// If the bag is empty, the human is unable to do some actions
			// (ex:attack, heal...)
			if (bag != null) {
				// The human cures itself with adapted items in his bag
				heal();
				// The human searches some food into his bag
				findFood();
				// Choose the weakest character
				Character c = chooseBestStartegy(updatedField);
				if (c != null)
					encounterCharacter(c);
			}
			if (Simulator.GenerateRandomBoolean()) {
				int births = breed();
				for (int b = 0; b < births; b++) {
					Location loc = updatedField.freeAdjacentLocation(
							getLocation(), 1);
					if (loc != null) {
						if (newActors == null)
							throw new NullPointerException(
									"null newActors passed in act method of human class.");

						Human newHuman = new Human(HP_HUMANS, updatedField, loc);
						newActors.add(newHuman);
						updatedField.place(newHuman, loc);
					}
				}
			}
			Location newLocation = updatedField.freeAdjacentLocation(
					getLocation(), SPEED);
			if (newLocation != null) {
				setLocation(newLocation);
				updatedField.place(this, newLocation);
			}
		}
	}

	/**
	 * @author Antoine Lavail
	 * 
	 * Put an item into the bag
	 * 
	 * @param i
	 *            : The specified item.
	 */
	public void putIntoBag(Item i) {
		if (i == null)
			throw new NullPointerException(
					"null item passed in putIntoBag method of human class.");

		if (bag == null) {
			bag = new ArrayList<Item>();
		}
		bag.add(i);
	}

	/**
	 * @author Antoine Lavail
	 * 
	 * Find something to heal into the bag 
	 * 
	 * @param 
	 *         
	 */
	private void heal() {
		if (getHealthPoints() < HP_HUMANS) {
			Iterator<Item> iterator = bag.iterator();
			while (iterator.hasNext()) {
				Item item = iterator.next();
				if (item.getTypeItem() == Item.CODE_HEALTH_ITEM) {
					item.use(this);
					iterator.remove();
					if (getHealthPoints() == HP_HUMANS)
						break;
				}
			}
		}
	}

	/**
	 * @author Antoine Lavail
	 * 
	 * Find something to feed into the bag 
	 *         
	 */
	private void findFood() {
		if (turnsSinceLastMeal > 0) {
			Iterator<Item> i = bag.iterator();
			while (i.hasNext()) {
				Item item = i.next();
				if (item.getTypeItem() == Item.CODE_FOOD_ITEM) {
					item.use(this);
					i.remove();
					setTurnsSinceLastMeal(0);
				}
			}
		}
	}

	/**
	 * @author Romain Roufast
	 *  
	 * Collect some items and put them into the bag 
	 * 
	 * @param field
	 * 			: the field         
	 */
	private void collectItems(Field field) {
		if (field == null)
			throw new NullPointerException(
					"null field passed in collectItems method of human class.");

		List<Item> l = field.getItemsAt(getLocation());
		if (l != null) {

			Iterator<Item> iterator = l.iterator();
			while (iterator.hasNext()) {
				Item item = iterator.next();
				if ((item.getTypeItem() == Item.CODE_AMMO_ITEM)
						&& (bag != null)) {
					if (reloadWeapons((Ammo) item)) {
						iterator.remove();
					}
				} else if (item.getTypeItem() == Item.CODE_WAR_ITEM) {
					if (addWeapon((Weapon) item)) {
						iterator.remove();
					}
				} else
					putIntoBag(item);
			}
		}
	}

	/**
	 * @author Antoine Lavail & Romain Roufast
	 * 
	 * Reload weapons with the ammo just found
	 * 
	 * @param a
	 * 			: the specified ammo
	 * 
	 * @return remove	
	 * 			: return true if the ammo is used to delete it
	 */
	private boolean reloadWeapons(Ammo a) {
		if (a == null)
			throw new NullPointerException(
					"null ammo passed in reloadWeapons method of human class.");

		boolean remove = false;
		Iterator<Item> iterator = bag.iterator();

		while (iterator.hasNext()) {
			Item item = iterator.next();
			if ((item.getTypeItem() == Item.CODE_WAR_ITEM)
					&& (((Weapon) item).hasAmmo())
					&& (((RechargeableWeapon) item).getCategoryAmmo()).equals(a
							.getCategoryAmmo())
					&& !(((RechargeableWeapon) item).isFull())) {
				((RechargeableWeapon) item).reload(a);
				remove = true;
				break;
			}
		}
		return remove;
	}

	/**
	 * @author Antoine Lavail
	 * 
	 * See if the weapon is not already in the bag.
	 * 
	 * @param w
	 * 			: the specified weapon
	 * 
	  * @return remove	
	 * 			: return true if the ammo is used to delete it
	 */
	private boolean addWeapon(Weapon w) {
		if (w == null)
			throw new NullPointerException(
					"null weapon passed in addWeapon method of human class.");

		boolean remove = true;
		if (bag != null) {
			Iterator<Item> iterator = bag.iterator();
			while (iterator.hasNext()) {
				Item item = iterator.next();
				if ((item.getTypeItem() == Item.CODE_WAR_ITEM)
						&& (w.getCategory().equals(((Weapon) item)
								.getCategory()))) {
					if (((Weapon) item).hasAmmo()) {
						if (((RechargeableWeapon) item).isFull()) {
							remove = false;
							break;
						} else {
							iterator.remove();
							break;
						}
					} else {
						break;
					}
				}
			}
		}
		if (remove)
			putIntoBag(w);

		return remove;
	}

	/**
	 * @author Antoine Lavail
	 * 
	 * Choose the best weapon according to the breed of the character
	 * 
	 * @param b
	 *            , the breed of the character
	 * @return the weapon or null if there is no weapon in the bag
	 */
	private Weapon chooseBestWeapon(Breed b) {
		if (b == null)
			throw new NullPointerException(
					"null breed passed in chooseBestWeapon method of human class.");

		Weapon w = null;
		int damages = 0;
		if (bag != null) {
			for (Item i : bag) {
				// If the item is a weapon
				if (i.getTypeItem() == Item.CODE_WAR_ITEM) {
					if ((((Weapon) i).hasAmmo() && (((RechargeableWeapon) i)
							.remainsAmmunitions()))
							|| (!((Weapon) i).hasAmmo())) {
						int tmpDamageItem = ((Weapon) i).getDamages(b);
						if (tmpDamageItem > damages) {
							w = ((Weapon) i);
							damages = tmpDamageItem;
						}
					}
				}
			}
		}
		return w;
	}

	/**
	 * @author Antoine Lavail
	 * 
	 * Choose the best character to attack him
	 * 
	 * @param updatedField
	 * 				: the field
	 * 
	 * @return c
	 * 				: the best character
	 * 
	 */
	private Character chooseBestStartegy(Field updatedField) {
		if (updatedField == null)
			throw new NullPointerException(
					"null updatedField passed in chooseBestStrategy method of human class.");

		Character c = null;
		List<Character> l = this.aroundCharacter(updatedField);
		int damageMax = 0;
		if (l.size() > 0) {
			for (Character character : l) {
				if ((character.isAlive())
						&& (character.getBreed().equals(Breed.ZOMBIE)
								|| character.getBreed().equals(Breed.VAMPIRE) || character
								.getBreed().equals(Breed.MADZOMBIE))) {
					int tmpDamageMax = getSimulateDamages(character.getBreed());
					if (tmpDamageMax > damageMax) {
						damageMax = tmpDamageMax;
						c = character;
					}
				}
			}
		}
		return c;
	}

	/**
	 * @author Romain Roufast
	 * 
	 * Get the simulates damages for a breed
	 * 
	 * @param b
	 * 			: the specified breed
	 * 
	 * @return d
	 * 			: the damage inflicted
	 */
	private int getSimulateDamages(Breed b) {
		if (b == null)
			throw new NullPointerException(
					"null breed passed in getSimulateDamages method of human class.");

		int d = 0;
		Weapon w = chooseBestWeapon(b);
		if (w != null)
			d = w.getDamages(b);

		return d;
	}

	/**
	 * Return the bag
	 * 
	 * @return the bag
	 */
	public List<Item> getBag() {
		return bag;
	}

	/**
	 * Increment the age of the human
	 */
	public void incrementAge() {
		age++;
		if (age > MAX_AGE)
			setDead();
	}

	/**
	 * Set the human as dead
	 */
	public void setDead() {
		setHealthPoints(0);
		if (location != null) {
			field.clear(location);
			location = null;
			field = null;
		}
	}

	/**
	 * @author Alexis Laurent
	 * 
	 * Indicates whether the bag contains weapons
	 * 
	 * @return r
	 * 			: true if so
	 */
	private boolean containsWeapon() {
		boolean r = false;
		if (bag != null) {
			for (Item i : bag) {
				if (i.getTypeItem() == Item.CODE_WAR_ITEM)
					r = true;
			}
		}
		return r;
	}
}
